#include "h265_trans_h264.h"
#include "h265_stream.h"
#include "Util/System.h"
#include <sys/time.h>
#include <iostream>



void _LOG(const char *szFormat, ...) {
    char buff[2048] = {0,};
	va_list args;

	struct timeval curTime;
	gettimeofday(&curTime, NULL);
	int milli = curTime.tv_usec/1000;
	char buffer[80] = {0};
	struct tm nowTime;
	localtime_r(&curTime.tv_sec, &nowTime);
	strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &nowTime);

	va_start(args, szFormat);
    #ifdef _WIN32
	_vsnprintf(buff, 2047, szFormat,args);
    #else
	vsnprintf(buff, 2047, szFormat,args);
    #endif
	va_end(args);
	fprintf(stderr, "%s.%03d %s", buffer, milli, buff);
}

#define DEBUG_FILE_OUTPUT 0
#define USE_NEW_LOGAN_API 0 //是否使用新的转码sdk
#if USE_NEW_LOGAN_API
    #define H264_ENCODEC_NAME  "h264_ni_logan_enc"
    #define H265_DECODEC_NAME  "h265_ni_logan_dec"
#else
    #define H264_ENCODEC_NAME  "h264_ni_enc"
    #define H265_DECODEC_NAME  "h265_ni_dec"
#endif
class H265ToH264
{
public:
	H265ToH264(const char *stream);
	virtual ~H265ToH264();
	int transcode(uint8_t* inbuf, int insize, uint32_t pts, uint32_t dts, uint8_t* outbuf, uint* outsize,uint32_t *ppts, uint32_t *pdts, int* hasKeyFrame);
	int openH265Decoder(int width, int height);
	int openH264Encoder();
private:
	std::string m_streamid;
    #if DEBUG_FILE_OUTPUT
	FILE* m_fpOut;
    #endif
	AVCodecContext* m_pDecCodecCtx, * m_pEncCodecCtx;
	bool m_bgetfirstpts;
};

void my_log_printf(void* ptr, int level, const char* fmt, va_list vl) {
	if (level < AV_LOG_VERBOSE) {
	    _LOG("Ffmpeg [%d]: %s\n", level, fmt);
	}
}

H265ToH264::H265ToH264(const char *stream) {
	m_streamid = stream;
    #if DEBUG_FILE_OUTPUT
	m_fpOut = NULL;
    #endif
	m_pDecCodecCtx = NULL;
	m_pEncCodecCtx = NULL;
	av_log_set_level(AV_LOG_INFO);
	//av_log_set_callback(my_log_printf);  // 设置自定义的日志输出方法
	m_bgetfirstpts = false;
}

H265ToH264::~H265ToH264() {
	_LOG("[%s] closeH265Decoder!\n", m_streamid.c_str());
	if (m_pDecCodecCtx) {
		avcodec_close(m_pDecCodecCtx);
		avcodec_free_context(&m_pDecCodecCtx);
	}
	m_pDecCodecCtx = NULL;
	_LOG("[%s] closeH264Encoder!\n", m_streamid.c_str());
	if (m_pEncCodecCtx) {
		avcodec_close(m_pEncCodecCtx);
		avcodec_free_context(&m_pEncCodecCtx);
	}
	m_pEncCodecCtx = NULL;
    #if DEBUG_FILE_OUTPUT
	fclose(m_fpOut);
    #endif
    #if !defined(NDEBUG)
    toolkit::System::printcallstack("H265ToH264析构");
    #endif
}

int H265ToH264::openH264Encoder() {
	int ret;
	unsigned int i;
	_LOG("[%s] ---- h264 ni enc ---\n", m_streamid.c_str());
	if (m_pDecCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
		/* in this example, we choose transcoding to same codec */
		const AVCodec* _pEncCodec = avcodec_find_encoder_by_name(H264_ENCODEC_NAME);
		if (!_pEncCodec) {
			_LOG("[%s] Necessary encoder %s not found\n", m_streamid.c_str(),H264_ENCODEC_NAME);
			return AVERROR_INVALIDDATA;
		}
		m_pEncCodecCtx = avcodec_alloc_context3(_pEncCodec);
		if (!m_pEncCodecCtx) {
			_LOG("[%s] Failed to allocate the encoder context\n", m_streamid.c_str());
			return AVERROR(ENOMEM);
		}
		/* In this example, we transcode to same properties (picture size,
		* sample rate etc.). These properties can be changed for output
		* streams easily using filters */
		if (m_pDecCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
			m_pEncCodecCtx->height = m_pDecCodecCtx->height;
			m_pEncCodecCtx->width = m_pDecCodecCtx->width;
			m_pEncCodecCtx->sample_aspect_ratio = m_pDecCodecCtx->sample_aspect_ratio;
			/* take first format from list of supported formats */
			if (_pEncCodec->pix_fmts)
				m_pEncCodecCtx->pix_fmt = _pEncCodec->pix_fmts[0];
			else
				m_pEncCodecCtx->pix_fmt = m_pDecCodecCtx->pix_fmt;

			printf("%s:%d, set EncCodec timebase %d/%d, bit_rate %ld\n", __FUNCTION__, __LINE__, m_pDecCodecCtx->framerate.num, m_pDecCodecCtx->framerate.den, m_pDecCodecCtx->bit_rate);
			/* video time_base can be set to whatever is handy and supported by encoder */
			m_pEncCodecCtx->time_base = (AVRational){1, 25};//av_inv_q(m_pDecCodecCtx->framerate);
			m_pEncCodecCtx->framerate = (AVRational){25, 1};

			//av_opt_set(m_pEncCodecCtx->priv_data, "xcoder-params", "RcEnable=1:bitrate=2000000", 0);
			//av_opt_set(m_pEncCodecCtx->priv_data, "xcoder-params", "hvsQPEnable=1:hvsQpScale=2:maxDeltaQp=10:gopPresetIdx=9:intraPeriod=26:repeatHeaders=1:useLowDelayPocType=1:RcEnable=1:bitrate=2000000:RcInitDelay=3000:lowDelay=1", 0);
			av_opt_set(m_pEncCodecCtx->priv_data, "loglevel", "trace", 0);
			av_opt_set(m_pEncCodecCtx->priv_data, "xcoder-params", "hvsQPEnable=1:hvsQpScale=2:maxDeltaQp=10:gopPresetIdx=9:intraPeriod=26:repeatHeaders=1:useLowDelayPocType=1:RcEnable=1:bitrate=2000000:RcInitDelay=3000:lowDelay=1", 0);
			//m_pEncCodecCtx->bit_rate = 2000000;
		}

		//if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
		//  m_pEncCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

		/* Third parameter can be used to pass settings to encoder */
		ret = avcodec_open2(m_pEncCodecCtx, _pEncCodec, NULL);
		if (ret < 0) {
			_LOG("[%s] Cannot open video encoder \n", m_streamid.c_str());
			return ret;
		}
	} else {
		_LOG("[%s] Elementary stream is of unknown type, cannot proceed\n", m_streamid.c_str());
		return AVERROR_INVALIDDATA;
	}
	_LOG("[%s] Seccess Open H264 Encoder!\n", m_streamid.c_str());
    #if DEBUG_FILE_OUTPUT
	m_fpOut = fopen("test.264", "wb");
    #endif
	return 0;
}

int H265ToH264::openH265Decoder(int width, int height) {
	_LOG("[%s] ---- h265 ni dec ---\n", m_streamid.c_str());
	int ret = -1;
	//查找硬件解码器
	const AVCodec* _pDecCodec = avcodec_find_decoder_by_name(H265_DECODEC_NAME);
	if (_pDecCodec == NULL) {
        _LOG("[%s] Necessary encoder %s not found\n", m_streamid.c_str(),H265_DECODEC_NAME);
		return -1;
	}
	m_pDecCodecCtx = avcodec_alloc_context3(_pDecCodec);
	if (m_pDecCodecCtx == NULL) {
		printf("alloc context3 failed\n");
		return -1;
	}
	m_pDecCodecCtx->width = width;//1920;
	m_pDecCodecCtx->height = height;//1080;
	m_pDecCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	m_pDecCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    _LOG("解码器时间基,%d/%d-%d/%d\n",m_pDecCodecCtx->time_base.num,m_pDecCodecCtx->time_base.den,m_pDecCodecCtx->pkt_timebase.num,m_pDecCodecCtx->pkt_timebase.den);
	///打开解码器
	if (avcodec_open2(m_pDecCodecCtx, _pDecCodec, NULL) < 0) {
		avcodec_close(m_pDecCodecCtx);
        char errBuf[256]={0};
        av_make_error_string(errBuf,sizeof(errBuf),ret);
		printf("Could not open codec %s,ret:%d,%s\n",H265_DECODEC_NAME,ret,errBuf);
		return -2;
	}
	_LOG("[%s] Open %s seccess! - decctx:%x\n",H265_DECODEC_NAME, m_streamid.c_str(), m_pDecCodecCtx);
	return 0;
}
                            
int H265ToH264::transcode(uint8_t *inbuf, int insize, uint32_t pts, uint32_t dts, uint8_t *outbuf, uint *outsize,uint32_t *ppts, uint32_t *pdts, int *hasKeyFrame) {
	int ret = 0;
	AVFrame* dec_frame = av_frame_alloc();
    AVPacket* dec_pkt = av_packet_alloc();
    AVPacket* enc_pkt = av_packet_alloc();
    if(!m_pEncCodecCtx || !m_pDecCodecCtx) {
        goto OUTPUT;
    }
    
    dec_pkt->data = inbuf;
	dec_pkt->size = insize;
    dec_pkt->pts = pts;
    dec_pkt->dts = dts;
    
	enc_pkt->data = NULL;
	enc_pkt->size = 0;
    #if DEBUG_FILE_OUTPUT
    _LOG("输入帧,pts:%u,dts:%u,size:%d,dec_pkt->pts:%" PRId64 "\n",pts,dts,insize,dec_pkt->pts);
    #endif
	ret = avcodec_send_packet(m_pDecCodecCtx, dec_pkt);
    if(ret < 0) {
        char errBuf[256]={0};
        av_make_error_string(errBuf,sizeof(errBuf),ret);
		_LOG("avcodec_send_packet failed,ret:%d,%s\n",ret,errBuf);
        return ret;
    }
    while( ret >= 0 ) {
        ret = avcodec_receive_frame(m_pDecCodecCtx, dec_frame);
        if (ret == AVERROR(EAGAIN)){
            goto OUTPUT;
        }else if (ret == AVERROR_EOF){
            _LOG("decoder EOF reached\n");
            goto OUTPUT;
        }else if (ret < 0) {
            char errBuf[256]={0};
            av_make_error_string(errBuf,sizeof(errBuf),ret);
            _LOG("avcodec_receive_frame failed,ret:%d,%s\n",ret,errBuf);
            goto OUTPUT;
        }
        
        #if 0
		printf("h265_ni_dec seccess [%d] video filter need: video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d\n",
				dec_frame->linesize,
				m_pDecCodecCtx->width, m_pDecCodecCtx->height, m_pDecCodecCtx->pix_fmt,
				m_pDecCodecCtx->time_base.num, m_pDecCodecCtx->time_base.den,
				m_pDecCodecCtx->sample_aspect_ratio.num,
				m_pDecCodecCtx->sample_aspect_ratio.den);
        #endif
        #if DEBUG_FILE_OUTPUT
        _LOG("解码帧,pts:%" PRId64 "\n",dec_frame->pts);
        #endif
		dec_frame->pts = dec_frame->best_effort_timestamp;
        do
        {
            ret = avcodec_send_frame(m_pEncCodecCtx, dec_frame);
            // Encoder is too busy to receive frame, try sending frame again
            if (ret == AVERROR(EAGAIN))
            {
                av_log(NULL, AV_LOG_ERROR, "Resending frame. Try again.\n");
                continue;
            }
            // Frame send issue occured
            else if (ret < 0) {
                av_log(NULL, AV_LOG_FATAL, "Error sending frame for encoding. RC=%d\n", AVUNERROR(ret));
                avcodec_close(m_pEncCodecCtx);
                avcodec_free_context(&m_pEncCodecCtx);
                m_pEncCodecCtx = NULL;
                goto OUTPUT;
            }
            // Frame sent successfully
            else 
            break;
        }while (1);
        
        /* Run this loop if a frame was sucessfully sent during avcodec_send_frame() OR
            no frame was sent because this function is in flushing mode OR
            a frame was sucessfully received by avcodec_receive_packet() */
        while ( ret >= 0 )
        {
            av_log(NULL, AV_LOG_DEBUG, "Receiving packet\n");
            ret = avcodec_receive_packet(m_pEncCodecCtx, enc_pkt);

            // No packets in encoder output buffer but not end of stream either
            if (ret == AVERROR(EAGAIN)) {
                goto OUTPUT; // Exit this function to send next packet
            }
            // End of stream detected
            else if (ret == AVERROR_EOF) {
                av_log(NULL, AV_LOG_ERROR, "Encoder EOF reached\n");
                goto OUTPUT;
            }
            // Packet receive error occured
            else if (ret < 0) {
                av_log(NULL, AV_LOG_FATAL, "Error during encoding\n");
                avcodec_close(m_pEncCodecCtx);
                avcodec_free_context(&m_pEncCodecCtx);
                m_pEncCodecCtx = NULL;
                goto OUTPUT;
            }
            #if DEBUG_FILE_OUTPUT
            fwrite(enc_pkt->data, 1, enc_pkt->size, m_fpOut);
            #endif
            //av_packet_rescale_ts(enc_pkt, m_pEncCodecCtx->time_base, m_pEncCodecCtx->time_base);
            memcpy(outbuf, enc_pkt->data, enc_pkt->size);
            *outsize = (unsigned int)enc_pkt->size;
            *ppts = enc_pkt->pts;
            *pdts = enc_pkt->dts;
            *hasKeyFrame = enc_pkt->flags&AV_PKT_FLAG_KEY;
            #if DEBUG_FILE_OUTPUT
            av_log(NULL, AV_LOG_INFO, "编码帧 pts %" PRId64 " dts %" PRId64 " (size=%5d),key:%d\n",
                enc_pkt->pts,enc_pkt->dts, enc_pkt->size,*hasKeyFrame);
            #endif
            av_packet_unref(enc_pkt);
            ret = 1;
            goto OUTPUT;
        }
    }
OUTPUT:    
	av_frame_free(&dec_frame);
	av_packet_free(&dec_pkt);
	av_packet_free(&enc_pkt);
	return ret;
}

//==========================================================================
Easy_API EasyH265Trans_Handle Easy_APICALL InitTransCode(const char *stream, int width, int height) {
    H265ToH264 *transcode = new H265ToH264(stream);
    if (!transcode) {
	    return NULL;
    }
    //transcode->openH265Decoder(width, height);
    if (0 != transcode->openH265Decoder(width, height)) {
	    delete transcode;
	    return NULL;
    }
    if (0 != transcode->openH264Encoder()) {
        printf("failed openH264Encoder!\n");
	    delete transcode;
	    return NULL;
    }	
    return transcode;
}

Easy_API int Easy_APICALL TransCodeWork(EasyH265Trans_Handle handle, uint8_t *buffer, int size, uint32_t pts, uint32_t dts, uint8_t *bout, uint *outsize,uint32_t *ppts, uint32_t *pdts, int *hasKeyFrame) {
    if (handle == NULL) {
        return -1;
    }
    return ((H265ToH264*)handle)->transcode(buffer, size, pts, dts , bout, outsize,ppts,pdts, hasKeyFrame);
}

Easy_API void Easy_APICALL ReleaseTransCode(EasyH265Trans_Handle handle) {
    if (handle != NULL) {
        delete ((H265ToH264*)handle);
    }
}
